using Digitalmes.Application.Scada.Models;
using Digitalmes.Application.Services;
using Digitalmes.Domain.Entities.Mes.Masters;
using Digitalmes.Domain.Entities.Mes.Processes;
using Digitalmes.Domain.Entities.Mes.Productions;
using Digitalmes.Domain.Entities.Mes.Reworks;

namespace Digitalmes.Application.Scada.Services.Impl;

/// <summary>
/// 产品进站服务。
/// </summary>
internal sealed class InboundService(
    IRepository<ProdCurrentState> currentStateRepo,
    IRepository<ProdStepExecution> stepExecutionRepo,
    IRepository<ProdInbound> inboundRepo,
    IRepository<MdMaterial> materialRepo,
    IRepository<ReworkDoc> reworkDocRepo,
    IRoutingQueryService routingQueryService,
    IWorkstationQueryService workstationQueryService,
    IMaterialMatchService materialCheckService,
    IWorkOrderService workOrderService,
    ISysConfigQueryService sysConfigQueryService,
    IStandardPartsQueryService standardPartsQueryService) : IInboundService
{
    public async Task<ScadaResult> EntryAsync(InboundInput input, CancellationToken cancellationToken = default)
    {
        // 检索工序信息
        var (operation, workstation) = await workstationQueryService.GetOperationAsync(input.LineCode, input.WsCode);
        if (operation == null)
        {
            return ScadaResult.Error(ErrorCode.E1103);
        }

        // 查找零件码的当前状态，没有找到表示为首次上线（或是标件），产品首次上线须从对应工艺路线的首序上线。
        var currentState = await currentStateRepo.SingleOrDefaultAsync(m => m.Sn == input.SN && m.LineId == operation.LineId);
        if (currentState is null)
        {
            // 检查是否为标件，标件不参与生产，只用于设备点检。
            var (isStandardParts, needInspect) = await standardPartsQueryService.IsStandardPartsAndWorkAsync(input.SN, operation.LineId, workstation!.Id, cancellationToken);
            if (isStandardParts)
            {
                // 此工位没有设置点检，设备放行，反之告知设备点检
                return !needInspect ? ScadaResult.Error(ErrorCode.E0020) : ScadaResult.Error(ErrorCode.E0030);
            }

            // 非标准件，产品首次上线
            return await FirstInboundAsync(input, operation, workstation!, cancellationToken);
        }

        // 非首次上线（或是返工上线）
        return await InboundWorkAsync(currentState, input, operation, workstation!, cancellationToken);
    }

    /// <summary>
    /// 首次上线
    /// </summary>
    private async Task<ScadaResult> FirstInboundAsync(InboundInput input,
        ProcOperation operation,
        MdWorkstation workstation,
        CancellationToken cancellationToken = default)
    {
        // 首次上线
        // 检查产品码是否有设置，若没有设置会尝试从条码规则中提取产品码
        var productCode = input.ProductCode;
        if (string.IsNullOrWhiteSpace(productCode))
        {
            var (ok, productCode0) = await ExtractProductCodeViaSnAsync(input.SN);
            if (!ok)
            {
                return ScadaResult.Error(ErrorCode.E1102);
            }

            productCode = productCode0!;
        }

        // 校验产品条码
        var product = await materialRepo.FirstOrDefaultAsync(m => m.Code == productCode, cancellationToken);
        if (product is null)
        {
            return ScadaResult.Error(ErrorCode.E1104);
        }

        var isMatchRule = await materialCheckService.MatchAsync(input.SN, product.Rules);
        if (!isMatchRule)
        {
            return ScadaResult.Error(ErrorCode.E1106);
        }

        // 检查是否有设定工艺步骤
        var routingStep = await routingQueryService.GetRoutingStepAsync(product.Id, operation.LineId, operation.Id);
        if (routingStep is null)
        {
            return ScadaResult.Error(ErrorCode.E1107);
        }

        // 验证当前工位是否为首序，是否允许产品不必从首序上线
        var allowStartedNotInFirst = await sysConfigQueryService.IsYes(ConfigKeys.AllowStartedNotInFirst);
        if (!allowStartedNotInFirst)
        {
            var isFirstWorkstation = await routingQueryService.IsFirstOperationAsync(routingStep.RoutingId, operation.Id);
            if (!isFirstWorkstation)
            {
                return ScadaResult.Error(ErrorCode.E1108);
            }
        }

        // 按优先级找到可用工单
        var wo = await workOrderService.GetWoAndIncrementStartedQtyAsync(product.Id, cancellationToken);
        if (wo is null)
        {
            return ScadaResult.Error(ErrorCode.E1105);
        }

        // 新增产品生产进度数据
        ProdCurrentState currentState = new()
        {
            Sn = input.SN,
            Wo = wo,
            ProductId = product.Id,
            ProductCode = product.Code,
            LineId = operation.LineId,
            RoutingId = routingStep.RoutingId,
            OperationId = routingStep.OperationId,
            OperationCode = routingStep.Operation?.OpCode,
            WorkstationId = workstation.Id,
            WorkstationCode = workstation.WsCode,
            InboundTime = DateTime.Now,
            StartedTime = DateTime.Now,
            Stage = WorkStageEnum.In,
            Status = PassEnum.OK,
        };
        await currentStateRepo.InsertAsync(currentState, cancellationToken);

        // 新增进站信息
        ProdInbound inbound = new()
        {
            Sn = currentState.Sn,
            Wo = currentState.Wo,
            ProductId = currentState.ProductId,
            ProductCode = currentState.ProductCode,
            LineId = currentState.LineId,
            RoutingId = currentState.RoutingId,
            OperationId = currentState.OperationId,
            OperationCode = currentState.OperationCode,
            WorkstationId = currentState.WorkstationId,
            WorkstationCode = currentState.WorkstationCode,
            Operator = input.Operator,
            Shift = input.Shift,
            Rfid = input.Rfid,
            InboundTime = DateTime.Now,
        };
        await inboundRepo.InsertAsync(inbound, cancellationToken);

        return ScadaResult.Ok();
    }

    /// <summary>
    /// 非首次上线工作
    /// </summary>
    private async Task<ScadaResult> InboundWorkAsync(ProdCurrentState currentState,
        InboundInput input,
        ProcOperation operation,
        MdWorkstation workstation,
        CancellationToken cancellationToken = default)
    {
        // 已报废，不能再生产
        if (currentState.Status is PassEnum.Scrap)
        {
            return ScadaResult.Error(ErrorCode.E1110);
        }

        // 已完工，不能再生产
        if (currentState.IsCompelted)
        {
            return ScadaResult.Error(ErrorCode.E1111);
        }

        // 检查是否有设定工艺步骤（需确保同一产品在同一条产线上有且仅有一条工艺路线）
        var routingStep = await routingQueryService.GetRoutingStepAsync(currentState.ProductId, operation.LineId, operation.Id);
        if (routingStep is null)
        {
            return ScadaResult.Error(ErrorCode.E1107);
        }

        // 处理在线返工产品（在线返工线下开单，线上处理；离线返工根据具体情况都在线下处理）
        // 在出站时返工单据 Id 会置为 0。
        // 思考：若返工上线工序与上一次完成工序之间有关键工序，且上线工序非关键工序，应该如何跳过后序是关键工序校验？
        var isOnlineRework = currentState.InReworkDocId > 0; // 检查是否为在线返工产品
        if (isOnlineRework)
        {
            // 检查当前工序是否为指定的返工工序（在线返工）或是返工后要重新上线的工序（离线返工）
            var reworkDoc = await reworkDocRepo.GetByIdAsync(currentState.InReworkDocId, cancellationToken);
            if (reworkDoc != null)
            {
                // 若当前工序并非指定的要返工工序，则"放行"。
                if (reworkDoc.TargetOperationId != operation.Id)
                {
                    return ScadaResult.Error(ErrorCode.E0020);
                }

                // 在线返工：当前工序为指定的要返工工序，更改返工状态标识。
                if (reworkDoc.ReworkMode is ReworkModeEnum.Online)
                {
                    reworkDoc.Status = ReworkStatusEnum.Reworking;
                    await reworkDocRepo.UpdateAsync(reworkDoc, m => new
                    {
                        m.Status,
                    }, cancellationToken);
                }
            }
        }

        // 验证是否允许重复进站
        // 思考：在线返工，首次在目标上线工序与NG工序之间允许重复进站，期间按情况不允许重复进站
        if (!isOnlineRework && !routingStep.AllowRepeat)
        {
            // 不允许重复进站，检查此工站是否已作业
            var hasDone = await stepExecutionRepo.IsAnyAsync(m => m.Sn == currentState.Sn
                    && m.RoutingId == routingStep.RoutingId
                    && m.OperationId == routingStep.OperationId, cancellationToken);
            if (hasDone)
            {
                return ScadaResult.Error(ErrorCode.E1112);
            }
        }

        // 非首站时，验证上一关键工序是否已完成
        // 思考：返工上线，若目标工序在NG工序之后，不验证两者之间的关键工序
        var isFirstOperation = await routingQueryService.IsFirstOperationAsync(currentState.RoutingId, routingStep.OperationId);
        if (!isOnlineRework && !isFirstOperation)
        {
            // 查找最近的关键工艺步骤，验证是否已完成
            var criticalStep = await routingQueryService.GetLastCriticalStepAsync(currentState.RoutingId, routingStep.OperationId);
            if (criticalStep != null)
            {
                // 检查是否有跳过关键工序
                var stepExecution = await stepExecutionRepo.LastOrDefaultAsync(m => m.Sn == currentState.Sn
                    && m.RoutingId == criticalStep.RoutingId
                    && m.OperationId == criticalStep.OperationId, cancellationToken);
                if (stepExecution is null)
                {
                    return ScadaResult.Error(ErrorCode.E1113);
                }

                // 必须良品过站
                if (criticalStep.IsMustGood && !stepExecution.IsSuccess())
                {
                    return ScadaResult.Error(ErrorCode.E1114);
                }
            }
        }

        // 更新产品生产进度数据
        currentState.Stage = WorkStageEnum.In;
        currentState.InboundTime = DateTime.Now;
        currentState.OutboundTime = null; // 清空上一次出站时间
        currentState.Status = PassEnum.OK; // 重置到 OK 状态
        currentState.OperationId = routingStep.OperationId;
        currentState.OperationCode = routingStep.Operation?.OpCode;
        currentState.WorkstationId = workstation.Id;
        currentState.WorkstationCode = workstation.WsCode;
        await currentStateRepo.UpdateAsync(currentState, m => new
        {
            m.Stage,
            m.InboundTime,
            m.OutboundTime,
            m.Status,
            m.OperationId,
            m.OperationCode,
            m.WorkstationId,
            m.WorkstationCode,
        }, cancellationToken);

        // 新增进站记录
        // 注：即使已有进站信息（已进站但还未出站），也会产生一条新的进站记录，因为过程数据与进站关联，使用重复进站记录可能会产生脏数据。
        ProdInbound inbound = new()
        {
            Sn = currentState.Sn,
            Wo = currentState.Wo,
            ProductId = currentState.ProductId,
            ProductCode = currentState.ProductCode,
            LineId = currentState.LineId,
            RoutingId = currentState.RoutingId,
            OperationId = currentState.OperationId,
            OperationCode = currentState.OperationCode,
            WorkstationId = currentState.WorkstationId,
            WorkstationCode = currentState.WorkstationCode,
            Operator = input.Operator,
            Shift = input.Shift,
            Rfid = input.Rfid,
            InboundTime = DateTime.Now,
        };
        await inboundRepo.InsertAsync(inbound, cancellationToken);

        return ScadaResult.Ok();
    }

    /// <summary>
    /// 尝试从 SN 中提取产品编号，未能成功提取则返回 null。
    /// </summary>
    private async Task<(bool Ok, string? ProductCode)> ExtractProductCodeViaSnAsync(string SN)
    {
        // 是否允许从 SN 中提取产品编号
        if (await sysConfigQueryService.IsYes(ConfigKeys.AllowExtractProductCodeViaSn))
        {
            // 从配置中找到产品匹配规则，规则：[起始位置]-[长度]，其中起始位置从 0 开始。
            // 目前只允许配置一种规则。
            var productCodeRule = await sysConfigQueryService.GetValueAsync(ConfigKeys.RuleExtractProductCodeViaSn);
            if (!string.IsNullOrEmpty(productCodeRule))
            {
                var (ok, start, len) = StringUtils.SplitToInt(productCodeRule, '-');
                if (ok && StringUtils.TryExtract(SN, start, len, out var productCode))
                {
                    return (true, productCode);
                }
            }
        }

        return (false, default);
    }
}
